package com.gitee.wsl.io.buffer

import com.gitee.wsl.io.api.ByteSink
import com.gitee.wsl.lang.ByteOrder
import com.gitee.wsl.lang.bytearray.get
import kotlinx.io.Buffer
import kotlinx.io.RawSource
import kotlinx.io.readByteArray
import kotlin.math.min

/**
 * Enhance byte array implementing the Buffer interface which provides position/limit/remaining/capacity tracking, and
 * support for either little endian or big endian encoding of basic numeric types. See [ByteBufferBase] and [Buffer] for
 * more details.
 * @param capacity starting size of buffer in bytes
 * @param order defaults to little endian encoding of numeric types
 * @param isReadOnly true if for some reason opeations that change content should throw an exception
 * @param buf defaults to a ByteArray of specified [capacity]
 */
 class ByteBuffer(
    capacity: Int,
    order: ByteOrder = ByteOrder.BigEndian,
    isReadOnly: Boolean = false,
    override var buf: ByteArray = ByteArray(capacity)
): ByteBufferBase<Byte, ByteArray>(capacity, order, isReadOnly), ByteSink {

    /**
     * Construct a ByteBuffer from an existing ByteArray
     * @param bytes becomes the buffer content (not a copy). capacity is set to the ByteArray size, position is set to
     * zero
     * @param order defaults to little endian encoding of numeric types
     */
    constructor(bytes: ByteArray, order: ByteOrder = ByteOrder.BigEndian) :
            this(bytes.size, order, false, bytes)

    override fun flip(): ByteBuffer {
        super.flip()
        return this
    }

    override fun getElementAt(index: Int): Byte {
        return buf[index]
    }

    override fun setElementAt(index: Int, element: Byte) {
        buf[index] = element
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned Int will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return INt will not have it's high order bits set.
     */
    override fun getElementAsInt(index: Int): Int {
        return buf toPosInt index
    }

    /**
     * gets one byte at the current position without changing the position, and return a UInt.
     * The byte is treated as unsigned.
     * @param index indicates which element in the current array to retrieve
     * @return UINt will not have it's high order bits set.
     */
    override fun getElementAsUInt(index: Int): UInt {
        return buf toPosUInt index
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned Long will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return Long will not have it's high order bits set.
     */
    override fun getElementAsLong(index: Int): Long {
        return buf toPosLong index
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned ULong will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return ULong will not have it's high order bits set.
     */
    override fun getElementAsULong(index: Int): ULong {
        return buf toPosULong index
    }

    override fun getBytes(length: Int): ByteArray {
        val l = min(remaining, length)
        val a = ByteArray(l)
        buf.copyInto(a, 0, position, position + l)
        position += l
        return a
    }

    override fun getBytes(bytes: ByteArray) {
        val l = min(remaining, bytes.size)
        buf.copyInto(bytes, 0, position, position + l)
        position += l
    }

    override fun put(bytes: ByteArray) {
        val l = min(remaining, bytes.size)
        bytes.copyInto(buf, position, 0, l)
        position += l
    }

    fun put(bytes: Byte){
        this.byte = bytes
    }

    fun put(value: Int){
        this.int = value
    }

    fun put(value: Long){
        this.long = value
    }

    fun put(value: Float){
        this.float = value
    }

    fun put(value: Double){
        this.double = value
    }

    fun put(value: Short){
        this.short = value
    }

    fun get():Byte = byte


    override fun write(source: ByteArray, startIndex: Int, endIndex: Int) {
        put(source[startIndex..endIndex].bytes)
    }

    override fun write(source: RawSource, byteCount: Long) {
        val buffer= Buffer().apply { source.readAtMostTo(this,byteCount)}
        put(buffer.readByteArray())
    }

    override fun transferFrom(source: RawSource): Long {
        TODO("Not yet implemented")
    }

    override fun writeByte(byte: Byte) = put(byte)

    override fun writeShort(short: Short) = put(short)

    override fun writeInt(int: Int) =put(int)

    override fun writeLong(long: Long) = put(long)

    override fun putEndian(bytes: ByteArray) {
        if (order == ByteOrder.LittleEndian) {
            bytes.reverse()
        }
        put(bytes)
    }

    override fun shortToArray(short: Short): ByteArray {
        return byteArrayOf(
            (short.toInt() shr 8).toByte(),
            short.toByte()
        )
    }

    override fun ushortToArray(ushort: UShort): ByteArray {
        return byteArrayOf(
            (ushort.toUInt() shr 8).toByte(),
            ushort.toByte()
        )
    }

    override fun intToArray(int: Int): ByteArray {
        return byteArrayOf(
            (int shr 24 and 0xff).toByte(),
            (int shr 16 and 0xff).toByte(),
            (int shr 8 and 0xff).toByte(),
            (int and 0xff).toByte()
        )
    }

    override fun uintToArray(int: UInt): ByteArray {
        return byteArrayOf(
            (int shr 24 and 0xffu).toByte(),
            (int shr 16 and 0xffu).toByte(),
            (int shr 8 and 0xffu).toByte(),
            (int and 0xffu).toByte()
        )
    }

    override fun longToArray(long: Long): ByteArray {
        return byteArrayOf(
            (long shr 56 and 0xff).toByte(),
            (long shr 48 and 0xff).toByte(),
            (long shr 40 and 0xff).toByte(),
            (long shr 32 and 0xff).toByte(),
            (long shr 24 and 0xff).toByte(),
            (long shr 16 and 0xff).toByte(),
            (long shr 8 and 0xff).toByte(),
            (long and 0xff).toByte()
        )
    }

    override fun ulongToArray(uLong: ULong): ByteArray {
        return byteArrayOf(
            (uLong shr 56 and 0xffu).toByte(),
            (uLong shr 48 and 0xffu).toByte(),
            (uLong shr 40 and 0xffu).toByte(),
            (uLong shr 32 and 0xffu).toByte(),
            (uLong shr 24 and 0xffu).toByte(),
            (uLong shr 16 and 0xffu).toByte(),
            (uLong shr 8 and 0xffu).toByte(),
            (uLong and 0xffu).toByte()
        )
    }

    /**
     * Similar to [ByteArray] copyInto
     * @param destination Array to receive copy
     * @param destinationOffset index into destination where copy will start
     * @param startIndex index in source array where copy will start
     * @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
     * be number of bytes copied.
     */
    override fun copyInto(
        destination: ByteArray,
        destinationOffset: Int,
        startIndex: Int,
        endIndex: Int
    ) {
        buf.copyInto(destination, destinationOffset, startIndex, endIndex)
    }

    override fun compareElement(element: Byte, other: Byte): Int {
        return element.compareTo(other)
    }

    /**
     * Increase size of buffer. Capacity and content are increased. Position is unchanged. if limit
     * currently set to capacity, it will be set to the new capacity. All data is retained from
     * previous buffer, including bytes between old limit and capacity.
     * @param addCapacity bytes to add. Unsigned as can't be used to shrink.
     */
    override fun expand(addCapacity: UInt) {
        val changeLimit = limit == capacity
        capacity += addCapacity.toInt()
        val newBuf = ByteArray(capacity)
        buf.copyInto(newBuf, 0, 0)
        buf = newBuf
        if (changeLimit) limit = capacity
    }

    /**
     * Appends a buffer starting at its position, to the end of this buffer,
     * starting at position of this buffer for appendBuffer remaining size. New position will
     * be at new limit. If the contents must be expanded (usually is), then a new byteArray is allocated.
     * position is moved to the new limit.
     * @param appendBuffer buffer to be appended, starting at its poition for remaining bytes
     * @return this
     */
    fun expand(appendBuffer: ByteBuffer) {
        val newLimit = position + appendBuffer.remaining
        if (newLimit > capacity) {
            val oldBuf = buf
            buf = ByteArray(newLimit)
            oldBuf.copyInto(buf, 0, 0, position)
        }
        appendBuffer.contentBytes.copyInto(
            buf,
            position,
            appendBuffer.position,
            appendBuffer.remaining
        )
        capacity = newLimit
        limit = newLimit
        position = limit
    }

    fun get(
        destination: ByteArray,
        destinationOffset: Int = 0,
        size: Int = destination.size
    ) {
        super.fillArray(destination, destinationOffset, size)
    }

    /**
     * Copies specified bytes from source to this buffer, starting at position, for the
     * specified length. Position is incremented by the length. Any bounds violation throws
     * and IllegalArgumentException
     *
     * @param source byte array to write from, will be unchanged
     * @param sourceOffset starting offset in source, defaults to 0
     * @param length number of bytes to copy, defaults to size of source
     * @return this
     * @throws IllegalArgumentException on bounds violation
     */
    fun putBytes(source: ByteArray, sourceOffset: Int = 0, length: Int = source.size) {
        checkBounds(sourceOffset, length, length)
        if (length > remaining)
            throw IllegalArgumentException("Length:$length exceeds remaining:$remaining")
        source.copyInto(buf, position, sourceOffset, length)
        position += length
    }

    /**
     * Make a new ByteBuffer containing the [remaining bytes] of this one. Length can be overridden to
     * a shorter value than the default [remaining]. If length is > [remaining], [remaining] is used.
     *
     * Position in this ByteBuffer is unaffected. Position in new returned ByteBuffer is 0.
     *
     * @param length defaults to [remaining]. can be between 1 and [remaining]
     */
    override fun slice(length: Int): ByteBuffer {
        val l = min(remaining, length)
        val bytes = ByteArray(l)
        buf.copyInto(bytes, 0, position, position + l)
        return ByteBuffer(bytes, this.order)
    }

    /**
     * Convert from a [ByteBuffer] to a [UByteBuffer], retaining the same capacity, position, limit
     * and contents
     */
    @OptIn(ExperimentalUnsignedTypes::class)
    fun toUByteBuffer(): UByteBuffer {
        val uBuf = UByteBuffer(capacity, order, isReadOnly, contentBytes.toUByteArray())
        uBuf.positionLimit(position, remaining)
        return uBuf
    }

    override fun toString(): String {
        return buildString {
            append("Position: $position, limit: $limit, remaining: $remaining. Content: 0x")
            for (i in position until limit) {
                append("${contentBytes[i].toString(16).padStart(2, '0')} ")
            }
        }
    }
    /**
     * Tells whether or not this buffer is equal to another object.
     *
     * <p> Two byte buffers are equal if, and only if,
     *
     * <ol>
     *
     *   <li><p> They have the same element type,  </p></li>
     *
     *   <li><p> They have the same number of remaining elements, and
     *   </p></li>
     *
     *   <li><p> The two sequences of remaining elements, considered
     *   independently of their starting positions, are pointwise equal.

     *   </p></li>
     *
     * </ol>
     *
     * <p> A byte buffer is not equal to any other type of object.  </p>
     *
     * @param other The object to which this buffer is to be compared
     *
     * @return <tt>true</tt> if, and only if, this buffer is equal to the
     *           given object
     */
    override fun equals(other: Any?): Boolean {
        if (other == null) return false
        if (this === other) return true
        if (other !is ByteBuffer) return false
        if (remaining != other.remaining) return false
        var i = limit - 1
        var j = other.limit - 1
        while (i >= position) {
            if (buf[i--] != other.buf[j--]) return false
        }
        return true
    }

    /**
     * Returns the current hash code of this buffer.
     *
     * <p> The hash code of a byte buffer depends only upon its remaining
     * elements; that is, upon the elements from <tt>position()</tt> up to, and
     * including, the element at <tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>.
     *
     * <p> Because buffer hash codes are content-dependent, it is inadvisable
     * to use buffers as keys in hash maps or similar data structures unless it
     * is known that their contents will not change.  </p>
     *
     * @return The current hash code of this buffer
     */
    override fun hashCode(): Int {
        var h = 1
        val p: Int = position
        for (i in limit - 1 downTo p) h = 31 * h + buf[i].toInt()
        return h
    }



    companion object{

        fun allocate(i: Int,byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteBuffer = ByteBuffer(2,byteOrder)

    }
}

fun<T> ByteBuffer.peck(readBlock: ByteBuffer.()-> T ): T {
    val old_position = position
    val ret = readBlock()
    position = old_position
    return ret
}

fun<T> ByteBuffer.peck(index: Int,readBlock: ByteBuffer.()->T): T {
    val old_position = position
    position = index
    val ret = readBlock()
    position = old_position
    return ret
}

fun ByteArray.buffered(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN) = ByteBuffer(this,byteOrder)
